﻿using System;
using System.Collections.Generic;
using System.Linq;
using TfsCloudDeploy.Deployment.Domain;
using System.IO;
using System.Text.RegularExpressions;
using TfsCloudDeploy.Deployment.Framework.Interfaces;
using TfsCloudDeploy.Deployment.Framework.Configuration;

namespace TfsCloudDeploy.Deployment.Framework.Helpers
{
    /// <summary>
    /// The utility methods for retrieving deployment information.
    /// </summary>
    /// <remarks>
    /// A typical structure of a deployment tree and how it maps to the Domain objects:
    ///     [root]
    ///       |--   ELS03                 ---- ReleaseEnvironment (QA)
    ///           |--   AUTH              ---- Deployment
    ///           |--   DA
    ///           |--   IDENTITYPROVIDER
    ///           |--   MAILCLEANUP
    ///           |--   MANAGE
    ///           |--   PA
    ///                |--   Deploy.PA.QA_20120329.1              ---- DeploymentBuild
    ///                    |--   Logs                             ---- DeploymentBuildLog
    ///                        |--   host_ELS03CAVWEB01.succeeded
    ///                        |--   host_ELS03CAVWEB01.err
    ///                        |--   host_ELS03CAVWEB01.log
    ///                        |--   host_ELS03CAVWEB01.pre.log
    ///                        |--   stats.log                    ---- DeploymentBuildLogMetadata
    ///                    |--   Logs-20120329_103352
    ///                    |--   Logs-20120329_103521
    ///                |--   Deploy.PA.QA_20120330.1
    ///                |--   Deploy.PA.QA_20120330.2
    ///                |--   Deploy.PA.QA_20120330.3
    ///           |--   PARSER
    ///           |--   TRANSFER
    ///           |--   WS
    /// </remarks>
    public static class DeploymentsHelper
    {
        #region Logging

        private static readonly ILog _log = new Log(typeof(DeploymentsHelper));

        #endregion

        private const string HOST_NAME_REGEX = @"^host_(?<HostName>[^.]+)\.(?:failed|succeeded|inprogress)$";
        private const string FILE_NAME_TEMPLATE = "{0}{1}"; // <filename><extension>
        private const string STATUS_INPROGRESS_EXTENSION = ".inprogress";
        private const string STATUS_SUCCEEDED_EXTENSION = ".succeeded";
        private const string STATUS_FAILED_EXTENSION = ".failed";
        private const string HOST_RESULTS_EXTENSION = ".log";
        private const string HOST_PREREQS_EXTENSION = ".pre.log";
        private const string HOST_ERRORS_EXTENSION = ".err";
        private const string DEPLOYMENTBUILDS_SEARCH_PATTERN = "*";
        private const string DEPLOYMENTS_SEARCH_PATTERN = "*";
        private const string HOSTS_SEARCH_PATTERN = "host_*.*";
        private const string STATS_METADATA_FILENAME = "stats.log";
        private const string DEPLOYMENT_LOGS_FOLDERNAME = "Logs";

        public static ReleaseEnvironment GetReleaseEnvironment()
        {
            return new ReleaseEnvironment()
            {
                SiteName = ConfigurationSettings.ReleaseEnvironmentName,
                SiteRootPath = ConfigurationSettings.SiteRootPath,
            };
        }

        public static DeploymentBuild GetDeploymentBuildForPackage(string packageName)
        {
            // Assemble the path from the package name:
            string deploymentPath = Path.Combine(ConfigurationSettings.SiteRootPath, packageName);
            if (Directory.Exists(deploymentPath))
            {
                _log.InfoFormat("GetDeploymentBuildForPackage: Package: {0}. deploymentPath does not exist: {1}.", packageName, deploymentPath);

                return GetCurrentDeploymentBuild(GetCurrentDeploymentBuildFolder(deploymentPath));
            }

            return null;
        }

        public static IEnumerable<Domain.Deployment> GetDeployments(string siteRootPath)
        {
            if (Directory.Exists(siteRootPath))
            {
                DirectoryInfo rootFolder = new DirectoryInfo(siteRootPath);
                DirectoryInfo[] childFolders = rootFolder.GetDirectories(DEPLOYMENTS_SEARCH_PATTERN, SearchOption.TopDirectoryOnly);

                if (childFolders.Length > 0)
                {
                    // Get all children:
                    var foldersList = childFolders.Select(
                        (childFolder) =>
                        {
                            string currentDeploymentBuildFolder = GetCurrentDeploymentBuildFolder(childFolder.FullName);
                            if (string.IsNullOrWhiteSpace(currentDeploymentBuildFolder))
                            {
                                return null;
                            }

                            DeploymentBuild currentDeploymentBuild = GetCurrentDeploymentBuild(currentDeploymentBuildFolder);

                            return new Domain.Deployment()
                            {
                                DeploymentName = childFolder.Name,
                                DeploymentRootPath = childFolder.FullName,
                                LastDeploymentStatus = GetDeploymentStatusForDeploymentBuild(currentDeploymentBuild.PackageBuildRoot),
                                CurrentDeploymentBuild = currentDeploymentBuild
                            };
                        }
                        );

                    return foldersList;
                }
            }
            else
            {
                _log.InfoFormat("GetDeployments: siteRootPath does not exist: {0}.", siteRootPath);
            }

            return new Domain.Deployment[] { };
        }

        public static DeploymentStatus GetDeploymentStatusForDeployment(string deploymentPath)
        {
            DirectoryInfo rootFolder = new DirectoryInfo(deploymentPath);
            DirectoryInfo[] childFolders = rootFolder.GetDirectories(DEPLOYMENTBUILDS_SEARCH_PATTERN, SearchOption.TopDirectoryOnly);

            if (childFolders.Length > 0)
            {
                var sortedFolders = from childFolder in childFolders
                                    orderby childFolder.Name descending
                                    select childFolder;

                DirectoryInfo latestDeploymentBuildFolder = sortedFolders.FirstOrDefault();
                return GetDeploymentStatusForDeploymentBuild(latestDeploymentBuildFolder);
            }

            return DeploymentStatus.Ready;
        }

        public static DeploymentStatus GetDeploymentStatusForDeploymentBuild(string deploymentBuildPath)
        {
            return GetDeploymentStatusForDeploymentBuild(new DirectoryInfo(deploymentBuildPath));
        }

        public static DeploymentStatus GetDeploymentStatusForDeploymentBuild(DirectoryInfo deploymentBuildDirectoryInfo)
        {
            string logsFolder = Path.Combine(deploymentBuildDirectoryInfo.FullName, DEPLOYMENT_LOGS_FOLDERNAME);
            if (Directory.Exists(logsFolder))
            {
                return GetAggregatedStatus(logsFolder);
            }
            else
            {
                _log.InfoFormat("GetDeploymentStatusForDeploymentBuild: logsFolder does not exist: {0}.", logsFolder);
            }

            return DeploymentStatus.Ready;
        }

        public static DeploymentStatus GetAggregatedStatus(string logsFolder)
        {
            DirectoryInfo rootFolder = new DirectoryInfo(logsFolder);
            FileInfo[] logFiles = rootFolder.GetFiles(HOSTS_SEARCH_PATTERN, SearchOption.TopDirectoryOnly);

            if (logFiles.Length > 0)
            {
                bool hasInProgressHosts = logFiles.Any((logFile) => { return logFile.Extension.ToLowerInvariant().Equals(STATUS_INPROGRESS_EXTENSION); });
                if (hasInProgressHosts)
                {
                    return DeploymentStatus.InProgress;
                }

                bool hasFailedHosts = logFiles.Any((logFile) => { return logFile.Extension.ToLowerInvariant().Equals(STATUS_FAILED_EXTENSION); });
                if (hasFailedHosts)
                {
                    return DeploymentStatus.Failed;
                }

                return DeploymentStatus.Succeeded;
            }
            else
            {
                _log.InfoFormat("GetAggregatedStatus: No log files exist: {0}.", logsFolder);
            }

            return DeploymentStatus.Ready;
        }

        public static string GetCurrentDeploymentBuildFolder(string deploymentRootPath)
        {
            DirectoryInfo rootFolder = new DirectoryInfo(deploymentRootPath);
            DirectoryInfo[] childFolders = rootFolder.GetDirectories(DEPLOYMENTBUILDS_SEARCH_PATTERN, SearchOption.TopDirectoryOnly);

            if (childFolders.Length > 0)
            {
                DirectoryInfo[] sortedDeploymentFolders = (from childFolder in childFolders
                                                           orderby childFolder.Name descending
                                                           select childFolder).ToArray<DirectoryInfo>();
                return sortedDeploymentFolders[0].FullName;
            }
            else
            {
                _log.InfoFormat("GetCurrentDeploymentBuildFolder: No deployment builds found under deployment folder: {0}.", deploymentRootPath);
            }

            return null;
        }

        public static DeploymentBuild GetCurrentDeploymentBuild(string deploymentBuildRootPath)
        {
            if (string.IsNullOrWhiteSpace(deploymentBuildRootPath))
            {
                return null;
            }

            string logsFolder = Path.Combine(deploymentBuildRootPath, DEPLOYMENT_LOGS_FOLDERNAME);
            if (!Directory.Exists(logsFolder))
            {
                _log.InfoFormat("GetCurrentDeploymentBuild: No logs folder found under deployment build folder: {0}.", deploymentBuildRootPath);

                DeploymentBuild deploymentBuild = new DeploymentBuild()
                {
                    PackageBuildRoot = deploymentBuildRootPath,
                    PackageBuildName = GetBuildNameFromPackageBuildRoot(deploymentBuildRootPath),
                    LatestDeploymentBuildLog = new DeploymentBuildLog()
                    {
                        LogFolderPath = logsFolder,
                        LogMetadata = null,
                        DeploymentBuildTargetHosts = null
                    }
                };
                return deploymentBuild;
            }

            DirectoryInfo rootFolder = new DirectoryInfo(logsFolder);
            FileInfo[] logFiles = rootFolder.GetFiles(HOSTS_SEARCH_PATTERN, SearchOption.TopDirectoryOnly);

            if (logFiles.Length > 0)
            {
                _log.InfoFormat("GetCurrentDeploymentBuild: Log files count: {0}.", logFiles.Length);

                IEnumerable<FileInfo> statusFiles = logFiles.Where((logFile) =>
                {
                    string extension = logFile.Extension.ToLowerInvariant();
                    return (extension.Equals(STATUS_INPROGRESS_EXTENSION) || extension.Equals(STATUS_FAILED_EXTENSION) || extension.Equals(STATUS_SUCCEEDED_EXTENSION));
                });

                if (statusFiles.Count() > 0)
                {
                    DeploymentBuild deploymentBuild = new DeploymentBuild()
                    {
                        PackageBuildRoot = deploymentBuildRootPath,
                        PackageBuildName = GetBuildNameFromPackageBuildRoot(deploymentBuildRootPath),
                        LatestDeploymentBuildLog = new DeploymentBuildLog()
                        {
                            LogFolderPath = logsFolder,
                            LogMetadata = GetDeploymentBuildLogMetadata(rootFolder),
                            DeploymentBuildTargetHosts = GetDeploymentBuildTargetHosts(statusFiles)
                        }
                    };

                    return deploymentBuild;
                }
                else
                {
                    _log.InfoFormat("GetCurrentDeploymentBuild: No status files found under logs folder: {0}.", logsFolder);
                }
            }

            return null;
        }

        public static IEnumerable<DeploymentBuildTargetHost> GetDeploymentBuildTargetHosts(IEnumerable<FileInfo> statusFiles)
        {
            IEnumerable<FileInfo> sortedStatusFiles = (from statusFile in statusFiles
                                                       orderby statusFile.Name.Substring(0, statusFile.Name.Length - statusFile.Extension.Length)
                                                       select statusFile);

            var deploymentBuildTargetHostsList = sortedStatusFiles.Select(
                (statusFile) =>
                {
                    string parentFilePath = statusFile.DirectoryName;
                    string statusFileWithoutExtension = statusFile.Name.Substring(0, statusFile.Name.Length - statusFile.Extension.Length);

                    return new DeploymentBuildTargetHost()
                    {
                        HostName = GetHostName(statusFile.Name),
                        HostStatusFilePath = statusFile.FullName,
                        HostResultsLogFilePath = GetResultsFilePathFromStatusFile(statusFileWithoutExtension, parentFilePath),
                        HostPreRequisitesLogFilePath = GetPreReqsFilePathFromStatusFile(statusFileWithoutExtension, parentFilePath),
                        HostErrorsLogFilePath = GetErrorsFilePathFromStatusFile(statusFileWithoutExtension, parentFilePath),
                        LastDeploymentStatus = GetHostDeploymentStatus(statusFile.Extension)
                    };
                }
                );

            return deploymentBuildTargetHostsList;
        }

        public static string GetHostName(string statusFileName)
        {
            Match match = Regex.Match(statusFileName, HOST_NAME_REGEX, RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
            if (match.Groups.Count > 0)
            {
                return match.Groups["HostName"].Value;
            }
            return null;
        }

        public static string GetPreReqsFilePathFromStatusFile(string statusFileWithoutExtension, string parentFilePath)
        {
            string filename = string.Format(FILE_NAME_TEMPLATE, statusFileWithoutExtension, HOST_PREREQS_EXTENSION);
            return Path.Combine(parentFilePath, filename);
        }

        public static string GetResultsFilePathFromStatusFile(string statusFileWithoutExtension, string parentFilePath)
        {
            string filename = string.Format(FILE_NAME_TEMPLATE, statusFileWithoutExtension, HOST_RESULTS_EXTENSION);
            return Path.Combine(parentFilePath, filename);
        }

        public static string GetErrorsFilePathFromStatusFile(string statusFileWithoutExtension, string parentFilePath)
        {
            string filename = string.Format(FILE_NAME_TEMPLATE, statusFileWithoutExtension, HOST_ERRORS_EXTENSION);
            return Path.Combine(parentFilePath, filename);
        }

        public static DeploymentStatus GetHostDeploymentStatus(string fileExtension)
        {
            switch (fileExtension)
            {
                case STATUS_INPROGRESS_EXTENSION:
                    return DeploymentStatus.InProgress;
                case STATUS_FAILED_EXTENSION:
                    return DeploymentStatus.Failed;
                case STATUS_SUCCEEDED_EXTENSION:
                    return DeploymentStatus.Succeeded;
                default:
                    return DeploymentStatus.Ready;
            }
        }

        public static DeploymentBuildLogMetadata GetDeploymentBuildLogMetadata(DirectoryInfo logFolder)
        {
            IEnumerable<FileInfo> statsLog = logFolder.EnumerateFiles(STATS_METADATA_FILENAME);
            if (statsLog.Count() > 0)
            {
                string metadataFilePath = statsLog.Single().FullName;
                string[] fileContents = File.ReadAllLines(metadataFilePath);

                if (fileContents.Length > 1)
                {
                    return new DeploymentBuildLogMetadata()
                    {
                        OperationName = fileContents[0].Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries)[1], // Command: Deploy
                        MetadataFilePath = metadataFilePath
                    };
                }
            }
            else
            {
                _log.WarnFormat("GetDeploymentBuildLogMetadata: No stats.log file found in logs folder: {0}", logFolder.FullName);
            }

            return null;
        }

        private static string GetBuildNameFromPackageBuildRoot(string packageBuildRoot)
        {
            int separatorIndex = packageBuildRoot.LastIndexOf('\\');

            if (separatorIndex >= 0)
            {
                return packageBuildRoot.Substring(separatorIndex + 1);
            }

            return packageBuildRoot;
        }
    }
}